package com.ebupt.migu.music.autotri.service.impl;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ebupt.migu.music.autotri.entity.PushResult;
import com.ebupt.migu.music.autotri.entity.push.ApiResponse;
import com.ebupt.migu.music.redis.controller.RedisDistributedLock;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.HtmlUtils;

import com.alibaba.fastjson.JSONObject;
import com.ebupt.migu.music.autotri.entity.message.Body;
import com.ebupt.migu.music.autotri.entity.message.MessageInfo;
import com.ebupt.migu.music.autotri.entity.message.UserSender;
import com.ebupt.migu.music.autotri.entity.push.PushContentObject;
import com.ebupt.migu.music.autotri.entity.push.PushPolicyObject;
import com.ebupt.migu.music.autotri.entity.push.PushRequestBody;
import com.ebupt.migu.music.autotri.mapper.AutoOperateMapper;
import com.ebupt.migu.music.autotri.service.AutoOperateService;
import com.ebupt.migu.music.commons.sms.Sender;
import com.ebupt.migu.music.commons.utils.DateUtil;
import com.ebupt.migu.music.commons.utils.HttpUtils;
import com.ebupt.migu.music.commons.utils.JsonUtils;
import com.ebupt.migu.restapi.IopMatchPost;

import lombok.extern.slf4j.Slf4j;

/**
 * @author liuyangyang@e-byte.com
 * @description: TODO
 * @date 2023/6/25 17:08
 */
@Slf4j
@Service
public class AutoOperateServiceImpl implements AutoOperateService {
    private final RedisDistributedLock redisDistributedLock;

    @Autowired
    public AutoOperateServiceImpl(RedisDistributedLock redisDistributedLock) {
        this.redisDistributedLock = redisDistributedLock;
    }

    /**
     * 个性运营参数替换
     */
    public static final Pattern REPLACE_PATTERN = Pattern.compile("(\\$[a-zA-Z]+)");
    /**
     * 短信内容提取
     */
    public static final Pattern EXTRACT_PATTERN = Pattern.compile("(?<=\\{)[^\\}]+");

    /**
     * push策略参数
     */
    @Value("${push.ip}")
    private String PUSH_IP;
    @Value("${push.app_id}")
    private String PUSH_APP_ID;
    @Value("${push.app_key}")
    private String PUSH_APP_KEY;
    @Value("${push.app_secret}")
    private String PUSH_APP_SECRET;
    @Value("${push.push_url}")
    private String PUSH_URL;

    /**
     * 消息通知策略参数
     */
    @Value("${msg.appid}")
    private String MSG_APP_ID;
    @Value("${msg.account}")
    private String MSG_ACCOUNT;
    @Value("${msg.password}")
    private String MSG_PASSWORD;
    @Value("${msg.userip}")
    private String MSG_USER_IP;
    @Value("${msg.appkey}")
    private String MSG_APP_KEY;
    @Value("${msg.channelid}")
    private String MSG_CHANNEL_ID;
    @Value("${msg.url}")
    private String MSG_URL;
    @Value("${push.push_result_duration}")
    private String pushResultDuration;
    @Value("${push.app_id}")
    private String APP_ID;
    @Value("${push.pushChannel}")
    private String pushChannel;
    @Value("${push.push_result_url}")
    private String PUSH_RESULT_URL;


    @Autowired
    private AutoOperateMapper autoOperateMapper;
    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 运营活动状态管控
     */
    @Override
    public void statusControl() {
        try {
            autoOperateMapper.updateAutoOperateActivityStart();
            autoOperateMapper.updateAutoOperateActivityEnd();
        } catch (Exception e) {
            log.error("更新自动运营活动状态失败:{}", e.getMessage());
        }
    }

    /**
     * 清理REDIS临时客群
     */
    @Override
    public void cleanRedisTempKey() {
        List<HashMap<String, String>> tacs = autoOperateMapper.queryTacInfoByTacStatus();
        for (HashMap<String, String> tac : tacs) {
            log.info("{}活动已过期，清理REDIS临时客群", tac.get("tacId"));
            redisTemplate.delete(tac.get("userGroupId") + "_" + tac.get("tacId") + "_phone_temp");
            redisTemplate.delete(tac.get("userGroupId") + "_" + tac.get("tacId") + "_uid_temp");
        }
    }


    /**
     * 原子自增
     *
     * @return
     */
    private void redis_incr(String key_name) {
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key_name, redisTemplate.getConnectionFactory());
        redisAtomicLong.getAndIncrement();
    }


    /**
     * 短信活动触发 tac mode = 1
     */
    @Override
    public void smsTrigger() {
        List<String> tacIds = queryTacIdByMode(1);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String day = sdf.format(new Date());
        for (String tacId : tacIds) {
            String redisLockKey = tacId + "-" + day;
            try {
                if (!redisDistributedLock.lock(redisLockKey, 1 * 60 * 30)) {
                    log.info("当前任务正在其他节点中被执行：{}", tacId);
                    continue;
                }
                String key_intersection = tacId + "_" + day + "_intersection";
                String key_black = tacId + "_" + day + "_black";
                String key_fail = tacId + "_" + day + "_fail";
                String key_success = tacId + "_" + day + "_success";
                Map<String, String> info = autoOperateMapper.queryAutoTriTacSMSByTacId(tacId);
                String iopTagId = autoOperateMapper.queryIopTagIdByTacId(tacId);
                IopMatchPost ip = new IopMatchPost();
                String userGroupId = info.get("userGroupId");
                String redisCustomerKey = userGroupId + "-phone";
                // 待运营的手机号不可为空集
                if (!redisTemplate.hasKey(redisCustomerKey) || redisTemplate.opsForSet().size(redisCustomerKey) == 0) {
                    log.info("活动ID：{}，当前未产生新的运营客群：{}", tacId, userGroupId);
                    continue;
                }
                log.info("短信活动触发，当前活动ID:{}，客群ID:{}", tacId, userGroupId);
                Cursor<String> cursor = redisTemplate.opsForSet().scan(userGroupId + "-phone", ScanOptions.NONE);
                int counter = 0;
                // 记录本轮遍历到的手机号
                Set<String> tmps = new HashSet<>();
                while (cursor.hasNext()) {
                    String msisdn = cursor.next();
                    tmps.add(msisdn);
                    // 校验用户是否存在于黑名单中，或者有触发历史
                    if (!checksum(msisdn, userGroupId, tacId, info.get("blacklistId"), "1")) {
                        redis_incr(key_black);
                        continue;
                    }
                    // 校验用户是否在iop标签名单中
                    if (null != iopTagId && !"".equals(iopTagId) && !ip.iopMatch(iopTagId, tacId, msisdn)) {
                        redis_incr(key_fail);
                        log.info("当前用户：{}不在iop标签中：{}", msisdn, iopTagId);
                        continue;
                    }
                    redis_incr(key_intersection);
                    // 短信模板id
                    String templateId = info.get("templateId");
                    // 短信模板内容
                    String content = info.get("smsContent");
                    // 匹配用户名，替换文本内容
                    content = replaceContent(content, msisdn);
                    // 提取短信参数
                    String[] params = extractContent(content);
                    // 发送短信
                    String[] mobile = {msisdn};
                    Sender.sendSMS(templateId, mobile, params);
                    redis_incr(key_success);
                    counter++;
                }
                redisTemplate.expire(key_intersection, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_black, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_success, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_fail, 60, TimeUnit.DAYS);
                // 删除本轮触发了下发过程的用户
                log.info("删除下发完成的客群：{}", tmps.size());
                String redisCustomerKeyBak = userGroupId + "-" + day + "-phone-bak";
                log.info("保存备份客群：{}", redisCustomerKeyBak);
                tmps.stream().forEach(tmp -> {
                    redisTemplate.opsForSet().remove(redisCustomerKey, tmp);
                    redisTemplate.opsForSet().add(redisCustomerKeyBak, tmp);
                });
                log.info("短信活动触发完成，当前活动ID:{}，客群ID:{}，本轮总计触发用户数量：{}", tacId, userGroupId, counter);
            } catch (Exception e) {
                log.error("当前任务下发过程发生异常：{}", tacId);
                e.printStackTrace();
            } finally {
                redisDistributedLock.unlock(redisLockKey);
            }
        }
    }

    /**
     * PUSH活动触发 tac mode = 2
     */
    @Override
    public void pushTrigger() {
        List<String> tacIds = queryTacIdByMode(2);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String day = sdf.format(new Date());
        for (String tacId : tacIds) {
            String redisLockKey = tacId + "-" + day;
            try {
                if (!redisDistributedLock.lock(redisLockKey, 1 * 60 * 30)) {
                    log.info("当前任务正在其他节点中被执行：{}", tacId);
                    continue;
                }
                String cid = "";
                String key_intersection = tacId + "_" + day + "_intersection";
                String key_black = tacId + "_" + day + "_black";
                String key_fail = tacId + "_" + day + "_fail";
                String key_success = tacId + "_" + day + "_success";
                Map<String, String> info = autoOperateMapper.queryAutoTriTacPUSHByTacId(tacId);
                String userGroupId = info.get("userGroupId");
                String redisCustomerKey = userGroupId + "-uid";
                // 待运营的uid不可为空集
                if (!redisTemplate.hasKey(redisCustomerKey) || redisTemplate.opsForSet().size(redisCustomerKey) == 0) {
                    log.info("活动ID：{}，当前未产生新的运营客群：{}", tacId, userGroupId);
                    continue;
                }
                log.info("PUSH活动触发，当前活动ID:{}，客群ID:{}", tacId, userGroupId);
                String iopTagId = autoOperateMapper.queryIopTagIdByTacId(tacId);
                IopMatchPost ip = new IopMatchPost();
                Cursor<String> cursor = redisTemplate.opsForSet().scan(userGroupId + "-uid", ScanOptions.NONE);
                int counter = 0;
                String taskDay = DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYYMMDD);
                String taskId = tacId + taskDay;
                // 记录本轮遍历到的uid
                Set<String> tmps = new HashSet<>();
                while (cursor.hasNext()) {
                    String uid = cursor.next();
                    tmps.add(uid);
                    // 校验用户是否存在于黑名单中，或者有触发历史
                    if (!checksum(uid, userGroupId, tacId, info.get("blacklistId"), "2")) {
                        redis_incr(key_black);
                        continue;
                    }
                    // 校验用户是否在iop标签名单中
                    if (null != iopTagId && !"".equals(iopTagId) && !ip.iopMatch(iopTagId, tacId, null, uid)) {
                        redis_incr(key_fail);
                        log.info("当前用户：{}不在iop标签中：{}", uid, iopTagId);
                        continue;
                    }
                    redis_incr(key_intersection);
                    // 构造PushContentObject实例
                    PushContentObject pushContentObject = new PushContentObject();
                    // 需要进行HTML解码
                    pushContentObject.setPushTitle(HtmlUtils.htmlUnescape(info.get("pushTitle")));
                    pushContentObject.setPushContent(HtmlUtils.htmlUnescape(info.get("pushContent")));
                    // 默认值是1
                    pushContentObject.setPushType(1);
                    // 1.前端输入链接以：mgmusic开头的，pushLinkType=100
                    // 2.输入链接以http或以https开头的，pushLinkType=9
                    if (null != info.get("pushUrl")) {
                        String pushURL = info.get("pushUrl");
                        if (pushURL.startsWith("mgmusic")) {
                            pushContentObject.setPushLinkType(100);
                        } else {
                            pushContentObject.setPushLinkType(9);
                        }
                        pushContentObject.setPushLinkAddress(pushURL);
                    }
                    // 多模板补白空格时，也不添加图片路径
                    if (!StringUtils.isBlank(info.get("picDir"))) {
                        String picDir = info.get("picDir");
                        pushContentObject.setImgUrl(picDir);
                    }
                    pushContentObject.setPushCreateTime(System.currentTimeMillis());
                    // 标题
                    String topic = info.get("pushTitle");
                    // push策略uuid数组
                    List<String> uids = new ArrayList<>();
                    uids.add(uid);
                    // 自动匹配用户 替换文本内容
                    topic = replaceContent(topic, uid);
                    pushContentObject.setPushContent(replaceContent(pushContentObject.getPushContent(), uid));
                    pushContentObject.setPushTitle(replaceContent(pushContentObject.getPushTitle(), uid));
                    PushRequestBody body = new PushRequestBody();
                    PushPolicyObject policy = new PushPolicyObject();
                    policy.setBroadcast(false);
                    policy.setIsOffline(true);
                    policy.setPushNetWorkType(0);
                    body.setPolicy(policy);
                    body.setAppId(PUSH_APP_ID);
                    body.setTaskId(taskId);
                    body.setContent(pushContentObject);
                    body.setUid(uids);
                    body.setDid(new ArrayList<>());
                    body.setTopic(HtmlUtils.htmlUnescape(topic));
                    body.setPushChannel("1");
                    body.setIsSync(true);
                    Map<String, Object> params = JsonUtils.beanToMap(body);
                    Map<String, String> header = header(taskId, body);
                    String url = PUSH_IP + PUSH_URL;
                    if (cid.equals("")) {
                        String res = HttpUtils.doPost(url, params, header);
                        ApiResponse apiResponse = JsonUtils.json2Obj(res, ApiResponse.class);
                        cid = String.valueOf(apiResponse.getData());
                    } else {
                        HttpUtils.doPost(url, params, header);
                    }
                    redis_incr(key_success);
                    counter++;
                }
                PushResult pushResult = new PushResult();
                pushResult.setCode(0);
                pushResult.setIsSync(0);
                pushResult.setJobType(1);
                pushResult.setPushDate(taskDay);
                pushResult.setStrategyId(tacId);
                pushResult.setResultBack(0);
                pushResult.setTaskId(taskId);
                pushResult.setCid(cid);
                // 是否需要创建效果统计记录：每天仅需要创建一条
                Boolean isPushResultExist = autoOperateMapper.isPushResultExist(tacId, day);
                if (null == isPushResultExist || !isPushResultExist) {
                    autoOperateMapper.createPushResult(pushResult);
                }
                redisTemplate.expire(key_intersection, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_black, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_success, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_fail, 60, TimeUnit.DAYS);
                // 删除本轮触发了下发过程的用户
                log.info("删除下发完成的客群：{}", tmps.size());
                String redisCustomerKeyBak = userGroupId + "-" + day + "-uid-bak";
                log.info("保存备份客群：{}", redisCustomerKeyBak);
                tmps.stream().forEach(tmp -> {
                    redisTemplate.opsForSet().remove(redisCustomerKey, tmp);
                    redisTemplate.opsForSet().add(redisCustomerKeyBak, tmp);
                });
                log.info("PUSH活动触发完成，当前活动ID:{}，客群ID:{}，任务ID【cid】:{}，本轮总计触发用户数量：{}", tacId, userGroupId, cid, counter);
            } catch (Exception e) {
                log.error("当前任务下发过程发生异常：{}", tacId);
                e.printStackTrace();
            } finally {
                // 删除分布式锁
                redisDistributedLock.unlock(redisLockKey);
            }
        }

    }

    /**
     * 消息通知活动触发 tac mode = 3
     */
    @Override
    public void messageTrigger() {
        List<String> tacIds = queryTacIdByMode(3);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String day = sdf.format(new Date());
        for (String tacId : tacIds) {
            String redisLockKey = tacId + "-" + day;
            try {
                if (!redisDistributedLock.lock(redisLockKey, 1 * 60 * 30)) {
                    log.info("当前任务正在其他节点中被执行：{}", tacId);
                    continue;
                }
                String key_intersection = tacId + "_" + day + "_intersection";
                String key_black = tacId + "_" + day + "_black";
                String key_fail = tacId + "_" + day + "_fail";
                String key_success = tacId + "_" + day + "_success";
                String key_err = tacId + "_" + day + "_err";
                Map<String, String> info = autoOperateMapper.queryAutoTriTacMessageByTacId(tacId);
                String userGroupId = info.get("userGroupId");
                String redisCustomerKey = userGroupId + "-uid";
                // 待运营的uid不可为空集
                if (!redisTemplate.hasKey(redisCustomerKey) || redisTemplate.opsForSet().size(redisCustomerKey) == 0) {
                    log.info("活动ID：{}，当前未产生新的运营客群：{}", tacId, userGroupId);
                    continue;
                }
                log.info("消息通知活动触发，当前活动ID:{}，客群ID:{}", tacId, userGroupId);
                String iopTagId = autoOperateMapper.queryIopTagIdByTacId(tacId);
                IopMatchPost ip = new IopMatchPost();
                Cursor<String> cursor = redisTemplate.opsForSet().scan(userGroupId + "-uid", ScanOptions.NONE);
                int counter = 0;
                // 生成task id
                Date date = new Date();
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtil.DATE_FORMAT_YYYYMMDD);
                String taskDate = simpleDateFormat.format(date);
                String taskID = tacId + taskDate;
                // 记录本轮遍历到的uid
                Set<String> tmps = new HashSet<>();
                while (cursor.hasNext()) {
                    String uid = cursor.next();
                    tmps.add(uid);
                    // 校验用户是否存在于黑名单中，或者有触发历史
                    if (!checksum(uid, userGroupId, tacId, info.get("blacklistId"), "3")) {
                        redis_incr(key_black);
                        continue;
                    }
                    // 校验用户是否在iop标签名单中
                    if (null != iopTagId && !"".equals(iopTagId) && !ip.iopMatch(iopTagId, tacId, null, uid)) {
                        log.info("当前用户：{}不在iop标签中：{}", uid, iopTagId);
                        redis_incr(key_fail);
                        continue;
                    }
                    List<String> uids = new ArrayList<>();
                    uids.add(uid);
                    Body body = new Body();
                    if (null != info.get("msgText")) {
                        body.setText(replaceContent(info.get("msgText"), uid));
                    }
                    if (!StringUtils.isBlank(info.get("msgPic"))) {
                        String msg_pic = info.get("msgPic");
                        try {
                            body.setPic(URLEncoder.encode(msg_pic, "UTF-8"));
                        } catch (UnsupportedEncodingException e) {
                            redis_incr(key_err);
                            log.error("pic's url encode error:{}", e.getMessage());
                        }
                    }
                    if (null != info.get("msgLink")) {
                        try {
                            body.setLink(URLEncoder.encode(info.get("msgLink"), "UTF-8"));
                        } catch (UnsupportedEncodingException e) {
                            redis_incr(key_err);
                            log.error("msg_link's url encode error:{}", e.getMessage());
                        }
                    }
                    UserSender userSender = new UserSender();
                    log.info("userSender:{}", userSender);
                    String icon = info.get("icon");
                    try {
                        userSender.setIcon(URLEncoder.encode(icon, "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        redis_incr(key_err);
                        log.error("icon's url encode error:{}", e.getMessage());
                    }
                    userSender.setNickName(HtmlUtils.htmlEscape(info.get("msgNickName")));
                    redis_incr(key_intersection);
                    body.setSender(userSender);
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setType(0);
                    messageInfo.setAction("11");
                    messageInfo.setBody(body);
                    // task id
                    messageInfo.setTaskId(taskID);
                    messageInfo.setTargetID(uid);
                    JSONObject params = new JSONObject();
                    params.put("userID", "-1");
                    params.put("messageInfo", messageInfo);
                    doPost(MSG_URL, params.toJSONString());
                    redis_incr(key_success);
                    counter++;
                }
                redisTemplate.expire(key_intersection, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_black, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_success, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_fail, 60, TimeUnit.DAYS);
                redisTemplate.expire(key_err, 60, TimeUnit.DAYS);
                // 删除本轮触发了下发过程的用户
                log.info("删除下发完成的客群：{}", tmps.size());
                String redisCustomerKeyBak = userGroupId + "-" + day + "-uid-bak";
                log.info("保存备份客群：{}", redisCustomerKeyBak);
                tmps.stream().forEach(tmp -> {
                    redisTemplate.opsForSet().remove(redisCustomerKey, tmp);
                    redisTemplate.opsForSet().add(redisCustomerKeyBak, tmp);
                });
                log.info("PUSH活动触发完成，当前活动ID:{}，客群ID:{}，任务ID【cid】:{}，本轮总计触发用户数量：{}", tacId, userGroupId, taskID, counter);
            } catch (Exception e) {
                log.error("当前任务下发过程发生异常：{}", tacId);
                e.printStackTrace();
            } finally {
                // 删除分布式锁
                redisDistributedLock.unlock(redisLockKey);
            }
        }
    }

    /**
     * 自动运营效果统计 策略类型：1-短信策略；2-push策略；3-消息通知；
     *
     * @param
     * @return
     */
    @Override
    public void autoOperationffectStatistics() {
        log.info("短信策略效果统计开始");
        //短信策略
        List<HashMap<String, String>> smsOperations = queryOperateByMode(1);
        change(smsOperations, 1);
        log.info("短信策略效果统计结束");
        log.info("push策略效果统计开始");
        //push策略
        List<HashMap<String, String>> pushOperations = queryOperateByMode(2);
        change(pushOperations, 2);
        log.info("push策略效果统计结束");
        log.info("消息通知效果统计开始");
        //消息通知
        List<HashMap<String, String>> smgOperations = queryOperateByMode(3);
        change(smgOperations, 3);
        log.info("消息通知效果统计结束");
    }

    /**
     * 组装当日统本地效果统计计数据
     *
     * @param
     * @return
     */
    private void change(List<HashMap<String, String>> perations, int tacMode) {
        perations.forEach(peration -> {
            log.info("peration:{}", peration.toString());
            String tacId = peration.get("tacId");
            log.info("tac_id:{}", tacId);
            List<String> days = autoOperateMapper.qryPushDate(tacId);
            days.stream().forEach(day -> {
                String key_intersection = tacId + "_" + day + "_intersection";
                String key_iop = tacId + "_" + day + "_iop";
                String key_black = tacId + "_" + day + "_black";
                String key_fail = tacId + "_" + day + "_fail";
                String key_success = tacId + "_" + day + "_success";
                String key_err = tacId + "_" + day + "_err";
                int intersectionUser = redisTemplate.hasKey(key_intersection) == null ? 0 : null == redisTemplate.opsForValue().get(key_intersection) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_intersection).toString());
                log.info("intersectionUser:{}", intersectionUser);
                int iopUser = redisTemplate.hasKey(key_iop) == null ? 0 : null == redisTemplate.opsForValue().get(key_iop) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_iop).toString());
                log.info("iopUser:{}", iopUser);
                int mbUser = null == autoOperateMapper.queryGroupByTacId(tacId) ? 0 : autoOperateMapper.queryGroupByTacId(tacId);
                log.info("mbUser:{}", mbUser);
                int blackUser = redisTemplate.hasKey(key_black) == null ? 0 : null == redisTemplate.opsForValue().get(key_black) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_black).toString());
                log.info("blackUser:{}", blackUser);
                int fail = redisTemplate.hasKey(key_fail) == null ? 0 : null == redisTemplate.opsForValue().get(key_fail) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_fail).toString());
                log.info("fail:{}", fail);
                int success = redisTemplate.hasKey(key_success) == null ? 0 : null == redisTemplate.opsForValue().get(key_success) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_success).toString());
                log.info("success:{}", success);
                int err = redisTemplate.hasKey(key_err) == null ? 0 : null == redisTemplate.opsForValue().get(key_err) ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(key_err).toString());
                log.info("err:{}", err);
                HashMap<String, Object> map = new HashMap<>();
                map.put("tacId", tacId);
                map.put("intersectionUser", intersectionUser);
                map.put("iopUser", iopUser);
                map.put("mbUser", mbUser);
                map.put("blackUser", blackUser);
                map.put("fail", fail);
                map.put("success", success);
                map.put("err", err);
                map.put("totalFailure", err + fail + blackUser);
                // 根据策略类型选择触点发送
                switch (tacMode) {
                    case 1:
                        // 短信策略
                        updateSmsOperations(map, tacId, day);
                        break;
                    case 2:
                        // push策略
                        updatePushOperations(map, tacId, day);
                        break;
                    case 3:
                        // 消息通知
                        updateSmgOperations(map, tacId, day);
                        break;
                    default:
                        break;
                }
            });
        });
    }

    /**
     * 自动运营效果统计-短信策略；
     *
     * @param map
     * @param tacId
     * @param date
     * @return
     */

    public void updateSmsOperations(HashMap<String, Object> map, String tacId, String date) {
        map.put("tacMode ", 1);
        try {
            //查询当天是否有效果统计数据
            Map<String, String> operates = autoOperateMapper.queryEffect("1", tacId, date);
            if (null != operates || !operates.isEmpty()) {
                autoOperateMapper.alterEffect(map);
                log.info("修改短信自动运营效果数据成功!任务id：{}", tacId);
            } else {
                autoOperateMapper.addEffect(map);
            }
        } catch (Exception exception) {
            log.info("短信自动运营效果数据更新失败，任务id：{}", tacId);
            exception.printStackTrace();
        }

    }

    /**
     * 自动运营效果统计-push策略；
     *
     * @param map
     * @param tacId
     * @param date
     * @return
     */

    public void updatePushOperations(HashMap<String, Object> map, String tacId, String date) {
        PushResult pushResult = autoOperateMapper.queryPushResult(tacId, date);
        if (null != pushResult) {
            boolean isCreate = false;
            String taskId = pushResult.getTaskId();
            long pushTime = pushResult.getCreateTime().getTime();
            long today = new Date().getTime();
            long distanceTime = today - pushTime;
            long resultDuration = (long) (Float.parseFloat(pushResultDuration) * 60 * 60 * 1000);
            // push推送48小时后去获取统计结果--只调一次
            if (distanceTime > resultDuration) {
                isCreate = true;
            }
            log.info("活动ID：{}，统计日期：{}", tacId, date);
            if (isCreate) {
                log.info("正在获取taskId：{}push效果数据", taskId);
                String result = PushResult(taskId);
                if (result != null) {
                    log.info("PUSH结果统计返回:{}", result);
                    cn.hutool.json.JSONObject jsonObject = new cn.hutool.json.JSONObject(result);
                    String code = jsonObject.getStr("code");
                    String msg = jsonObject.getStr("msg");
                    // 接口返回成功code
                    if (code.equals("000000")) {
                        cn.hutool.json.JSONObject data = jsonObject.getJSONObject("data");
                        Map<String, Object> paramMap = new HashMap<>();
                        paramMap.put("taskId", taskId);
                        // 百日联网数--对端用户去重
                        paramMap.put("msgTotal", data.getStr("msgTotal"));
                        // 在线用户数
                        paramMap.put("onlineNum", data.getStr("onlineNum"));
                        cn.hutool.json.JSONObject android = data.getJSONObject("android");
                        // 成功下发-android
                        int androidSend = Integer.parseInt(android.getStr("sent") != null ? android.getStr("sent") : "0");
                        // 展示数-android
                        int androidDisplayed =
                                Integer.parseInt(android.getStr("displayed") != null ? android.getStr("displayed") : "0");
                        // 点击数-android
                        int androidClicked =
                                Integer.parseInt(android.getStr("clicked") != null ? android.getStr("clicked") : "0");
                        cn.hutool.json.JSONObject ios = data.getJSONObject("ios");
                        // 展示数-ios
                        int iosDisplayed =
                                Integer.parseInt(ios.getStr("displayed") != null ? ios.getStr("displayed") : "0");
                        // 点击数-ios
                        int iosClicked = Integer.parseInt(ios.getStr("clicked") != null ? ios.getStr("clicked") : "0");
                        // 成功下发-ios
                        int iosSend = Integer.parseInt(ios.getStr("sent") != null ? ios.getStr("sent") : "0");
                        // 成功下发-总数
                        int totalSend = iosSend + androidSend;
                        // 展示数-总数
                        int totalDisplayed = iosDisplayed + androidDisplayed;
                        // 点击数-总数
                        int totalClicked = iosClicked + androidClicked;
                        log.info(
                                "androidSend:{}, " + "androidDisplayed:{}, " + "androidClicked:{}, " + "iosDisplayed:{}, "
                                        + "iosSend:{}, " + "iosClicked:{}, " + "totalSend:{}, " + "totalDisplayed:{}, "
                                        + "totalClicked:{}",
                                androidSend, androidDisplayed, androidClicked, iosDisplayed, iosSend, iosClicked, totalSend,
                                totalDisplayed, totalClicked);
                        // 转化率=点击数/成功下发数 --百分数
                        // 转化率采用字符串存储
                        String totalCrv = getProportion(totalClicked, totalDisplayed);
                        String iosCrv = getProportion(iosClicked, iosDisplayed);
                        String androidCrv = getProportion(androidClicked, androidDisplayed);
                        paramMap.put("tacId", tacId);
                        paramMap.put("androidSend", androidSend);
                        paramMap.put("androidDisplayed", androidDisplayed);
                        paramMap.put("androidClicked", androidClicked);
                        paramMap.put("iosDisplayed", iosDisplayed);
                        paramMap.put("iosClicked", iosClicked);
                        paramMap.put("iosSend", iosSend);
                        paramMap.put("totalSend", totalSend);
                        paramMap.put("totalDisplayed", totalDisplayed);
                        paramMap.put("totalClicked", totalClicked);
                        paramMap.put("totalCrv", totalCrv);
                        paramMap.put("iosCrv", iosCrv);
                        paramMap.put("androidCrv", androidCrv);
                        paramMap.put("pushTime", pushResult.getCreateTime());
                        int i = autoOperateMapper.selectByTaskId(taskId);
                        // 如果存在taskId 调用修改方法
                        if (i > 0) {
                            autoOperateMapper.updateStatisPush(paramMap);
                        } else {
                            autoOperateMapper.createStatisPush(paramMap);
                        }
                        log.info("调用获取PUSH效果统计接口groupresult.do==成功， create StatisticsPush:");
                        autoOperateMapper.updatePushResultStatus(tacId, pushResult.getPushDate());
                    } else {
                        log.info("调用获取PUSH效果统计接口groupresult.do失败");
                    }
                }
            }
            try {
                //查询当天是否有效果统计数据
                Map<String, String> operates = autoOperateMapper.queryEffect("2", tacId, date);
                log.info("map数据：{}", map);
                map.put("tacMode", 2);
                map.put("date", dateFormate(date));
                if (null != operates && !operates.isEmpty()) {
                    autoOperateMapper.alterEffect(map);
                    log.info("修改PUSH自动运营效果数据成功!任务id：{}", tacId);
                } else {
                    autoOperateMapper.addEffect(map);
                }
            } catch (Exception exception) {
                log.info("PUSH自动运营效果数据更新失败，任务id：{}", tacId);
                exception.printStackTrace();
            }
        } else {
            log.info("PUSH自动运营效果数据更新失败,无效果统计数据，任务id：{}", tacId);
        }

    }

    /**
     * 自动运营效果统计-消息通知；
     *
     * @param map
     * @param tacId
     * @param date
     * @return
     */
    public void updateSmgOperations(HashMap<String, Object> map, String tacId, String date) {
        try {
            //查询当天是否有效果统计数据
            Map<String, String> operates = autoOperateMapper.queryEffect("3", tacId, date);
            if (null == operates || operates.isEmpty()) {
                autoOperateMapper.alterEffect(map);
                log.info("修改消息通知自动运营效果数据成功!任务id：{}", tacId);
            } else {
                autoOperateMapper.addEffect(map);
            }
        } catch (Exception exception) {
            log.info("消息通知自动运营效果数据更新失败，任务id：{}", tacId);
            exception.printStackTrace();
        }

    }


    /**
     * 查询指定类型的且非暂停状态的活动ID列表 策略类型：1-短信策略；2-push策略；3-消息通知；4-banner；5-音乐之声
     *
     * @param tacMode
     * @return
     */
    public List<String> queryTacIdByMode(int tacMode) {
        List<String> tacIds = new ArrayList<>();
        try {
            tacIds = autoOperateMapper.queryTacIdByMode(tacMode);
        } catch (Exception e) {
            log.error("查询活动ID失败，活动类型：{}", tacMode);
        }
        return tacIds;
    }

    /**
     * 查询运营中且未结束的活动 策略类型：1-短信策略；2-push策略；3-消息通知；4-banner；5-音乐之声
     *
     * @param tacMode
     * @return
     */
    public List<HashMap<String, String>> queryOperateByMode(int tacMode) {
        List<HashMap<String, String>> operates = new ArrayList<>();
        try {
            operates = autoOperateMapper.queryOperateByMode(tacMode);
        } catch (Exception e) {
            log.error("查询活动失败，活动类型：{}", tacMode);
        }
        return operates;
    }

    public String PushResult(String taskId) {
        // 设置HTTP接口post参数
        if (StringUtils.isNotBlank(taskId)) {
            Map<String, Object> paramsMap = new HashMap<>();
            paramsMap.put("appId", APP_ID);
            paramsMap.put("taskId", taskId);
            paramsMap.put("pushChannel", pushChannel);
            paramsMap.put("appType", "music");
            Map<String, String> header = new HashMap<>();
            header.put("Content-Type", "application/json");
            header.put("charset", "utf-8");
            // 调用HTTP接口
            String result = "";
            try {
                result = HttpUtils.doPost(PUSH_IP + PUSH_RESULT_URL, paramsMap, header);
            } catch (Exception e) {
                log.error("调用PUSH结果统计接口失败", e);
            }
            return result;
        }
        return null;
    }

    /**
     * num1/num2的百分比
     *
     * @param num1
     * @param num2
     * @return
     */
    private static String getProportion(int num1, int num2) {
        if (num2 == 0) {
            return "0.00";
        }
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMaximumFractionDigits(2);
        String result = numberFormat.format((float) num1 / (float) num2 * 100);
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        if (pattern.matcher(result).matches()) {
            return result.concat(".00");
        }
        return result;
    }


    /**
     * 校验当前用户是否触发过活动
     *
     * @param userId
     * @param userGroupId
     * @param tacId
     * @param blacklistId
     * @param tacMode     1-短信活动，需要校验手机号，其余活动均需校验uid
     * @return
     */
    public boolean checksum(String userId, String userGroupId, String tacId, String blacklistId, String tacMode) {
        String redisKeyTemp;
        if ("1".equals(tacMode)) {
            redisKeyTemp = userGroupId + "_" + tacId + "_phone_temp";
        } else {
            redisKeyTemp = userGroupId + "_" + tacId + "_uid_temp";
        }
        // 初次下发活动，创建临时客群并写入首个用户ID
        if (!redisTemplate.hasKey(redisKeyTemp)) {
            log.info("{}：活动首次触发，创建临时客：{}，并将当前用户添加到临时客群：{}", tacId, redisKeyTemp, userId);
            redisTemplate.opsForSet().add(redisKeyTemp, userId);
            return true;
        }
        // 当前用户存在于临时客群中
        if (redisTemplate.opsForSet().isMember(redisKeyTemp, userId)) {
            log.info("用户：{}存在于临时客群：{}中，曾经触发过，本次触发忽略。活动ID：{}", userId, redisKeyTemp, tacId);
            return false;
        }
        // 当前用户存在于黑名单中
        if (!StringUtils.isEmpty(blacklistId) && autoOperateMapper.checkBlacklistMember(blacklistId, userId)) {
            log.info("当前用户：{}存在于黑名单中，本次触发忽略。活动ID：{}", userId, tacId);
            return false;
        }
        // 当前用户满足活动触发条件，将用户添加到临时客群
        redisTemplate.opsForSet().add(redisKeyTemp, userId);
        return true;
    }

    /**
     * 文本内容替换 替换各个触点的文本内容，达到千人千面 原内容：亲爱的$userName:白金会员$，您的$sing:歌曲$即将到期！ 从redis中读取关键的key 例如$userName $sing 能取到值为redis中的值
     * 如果redis中取不到值，则用文本中默认的内容 例如 userName--白金会员 sing--歌曲 替换后的（redis有值）： 亲爱的 redis值，您的 redis值 即将到期！ 替换后的（redis没有值）： 亲爱的
     * 白金会员，您的 歌曲 即将到期！
     */
    private String replaceContent(String context, String uid) {
        Matcher matcher = REPLACE_PATTERN.matcher(context);
        if (matcher.find()) {
            // 获取匹配key userName
            String key = matcher.group().substring(1);
            // 获取要替换的文本内容 $userName:白金会员$
            String str = context.substring(context.indexOf("$", matcher.start()), context.indexOf("$", matcher.end()) + 1);
            // 获取默认的值 白金会员
            String defaultVal = str.substring(str.indexOf(":") + 1, str.length() - 1);
            // 拼接redis key
            if ("userName".equals(key)) {
                // 默认用手机号或者uuid直接取用户名
                key = uid;
                // 从redis 用户信息 _user_info 中取数据
                context = redisTemplate.boundHashOps("_user_info").get(key) != null ? context.replace(str, String.valueOf(redisTemplate.boundHashOps("_user_info").get(key))) : context.replace(str, defaultVal);
            }
            // 递归再次调用该方法，直到需要替换的内容全部替换
            return replaceContent(context, uid);
        } else {
            return context;
        }
    }

    /**
     * 提取{}中的参数并以数组形式返回
     *
     * @param str
     * @return
     */
    private String[] extractContent(String str) {
        List<String> params = new ArrayList<>();
        Matcher matcher = EXTRACT_PATTERN.matcher(str);
        while (matcher.find()) {
            params.add(matcher.group());
        }
        // 提取不到参数
        if (null == params || params.size() == 0) {
            return null;
        }
        String[] tplVars = new String[params.size()];
        for (int i = 0; i < params.size(); i++) {
            tplVars[i] = params.get(i);
        }
        return tplVars;
    }

    /**
     * 请求签名 使用场景: 接入方发送HTTP请求时需要对请求做签名校验 签名方式: Map<String, Object> headers = new HashMap<>(); StringBuilder sb = new
     * StringBuilder("MIGU").append("\n"); sb.append(taskId).append("\n"); sb.append(pushChannel).append("\n");
     * sb.append(pushContent).append("\n"); sb.append(timestamp).append("\n"); sb.append(AppSecret).append("\n"); String
     * Authorization = AppKey + ":" + MD5Util.MD5(sb.toString()); headers.put("Authorization", Authorization);
     * headers.put("timestamp", timestamp); 其中: MIGU:固定值 \n:换行转义符 taskId: 任务id,唯一标示 pushChannel: 推送渠道1-个推 当前只支持个推渠道
     * pushContent:传输到推送系统的content.getPushContent() 详见Content Object timestamp:系统时间戳 AppKey :推送系统分配的appKey
     * AppSecret:推送系统分配的密钥 WARN: 接入方系统时间戳跟推送系统需要保持基本一致，防止重放攻击 推送说明: broadcast为true时表示针对app做推送 此时platform生效，表示对手机系统app推送
     * 如果推送列表 broadcast必为false, uid或者cid列表长度大于1。 当前只支持个推推送，所有pushChannel固定为1。 当uid或者cid长度为1时表示对单个用户进行推送。 md5值为小写md5
     */
    private Map<String, String> header(String taskId, PushRequestBody body) {
        Map<String, String> map = new HashMap<>();
        String timestamp = String.valueOf(System.currentTimeMillis());
        StringBuilder sb = new StringBuilder("MIGU").append("\n");
        sb.append(taskId).append("\n");
        sb.append(body.getPushChannel()).append("\n");
        sb.append(body.getContent().getPushContent()).append("\n");
        sb.append(timestamp).append("\n");
        sb.append(PUSH_APP_SECRET).append("\n");
        String Authorization = PUSH_APP_KEY + ":" + DigestUtils.md5Hex(sb.toString());
        map.put("Content-Type", "application/json; charset=utf-8");
        map.put("Authorization", Authorization);
        map.put("timestamp", timestamp);
        return map;
    }

    /**
     * HTTP请求
     *
     * @param url
     * @param request
     * @return
     */
    private void doPost(String url, String request) {
        RestTemplate restTemplate = new RestTemplate();
        // 设置编码集
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", authorization());
        HttpEntity<String> entity = new HttpEntity<>(request, headers);
        HttpEntity httpEntity = restTemplate.postForEntity(url, entity, String.class);
        log.info("请求参数：{}，消息通知返回结果：{}", request, httpEntity.getBody());
    }

    /**
     * header authorization -用户验证信息
     *
     * @return
     */
    private String authorization() {
        // part1： 密码
        String password = MSG_PASSWORD;
        // part2： 6位随机码
        String nonce = getRandom();
        // part3： created时间戳
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateUtil.DATE_TIME_FORMAT_YYYYMMDDHHMISS);
        String created = simpleDateFormat.format(date);
        // 加密
        String password_base_64 = base_64_encode(sha_256(password + nonce + created));
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("Basicappid=\"");
        stringBuffer.append(MSG_APP_ID);
        stringBuffer.append("\",account=\"");
        stringBuffer.append(MSG_ACCOUNT);
        stringBuffer.append("\",password=\"");
        stringBuffer.append(password_base_64);
        stringBuffer.append("\",nonce=\"");
        stringBuffer.append(nonce);
        stringBuffer.append("\",created=\"");
        stringBuffer.append(created);
        stringBuffer.append("\",userip=\"");
        stringBuffer.append(MSG_USER_IP);
        stringBuffer.append("\",appkey=\"");
        stringBuffer.append(MSG_APP_KEY);
        stringBuffer.append("\",channelid=\"");
        stringBuffer.append(MSG_CHANNEL_ID);
        stringBuffer.append("\"");
        String auth = stringBuffer.toString();
        log.info("header authorization -用户验证信息:{}", auth);
        return auth;
    }

    /**
     * sha-256加密算法
     *
     * @param str
     * @return
     */
    public static byte[] sha_256(String str) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes());
            return messageDigest.digest();
        } catch (NoSuchAlgorithmException e) {
            log.error("SHA-256加密出错:{}", e.getMessage());
        }
        log.error("sha-256加密失败");
        return null;
    }

    /**
     * 获取6位长度随机数
     *
     * @return
     */
    public static String getRandom() {
        StringBuffer val = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            val.append(random.nextInt(10));
        }
        return val.toString();
    }

    /**
     * base64进行加密
     *
     * @return
     */
    private static String base_64_encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    private String dateFormate(String dateStr) {
        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        LocalDate date = LocalDate.parse(dateStr, inputFormatter);
        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String formattedDate = date.format(outputFormatter);
        return formattedDate;
    }
}
